import RoomUtils from "../utils/room-utils";
import {BATTLE, GAME_OVER_TYPE, PAGE_STATUS, ROOM_STATUS, USER_STATUS} from "../configs/config";
import BattleDao from "../dao/battle-dao";
import BattleHistoryDao from "../dao/battle-history-dao";
import BattleUserDao from "../dao/battle-user-dao";
import UserStateDao from "../dao/user-state-dao";

const AsyncLock = require('async-lock');

// 锁机制
const lock = new AsyncLock();
const roomUtils = new RoomUtils();

// key: battleId, value: [{userId: '', allTime: '', stepTime: ''}]
const timeUserData: any = new Map();
// key: battleId, value: {isRedMove: false}
const battleData: any = new Map();

/**
 * 定时任务调度
 * @param io
 */
class JobUtils {

    constructor(io: any) {

        this.initResource();

        this.initAllTask(io);
    }

    /**
     * 系统启动时的相关数据放此处加载
     * 服务器重启后需要恢复的数据
     */
    initResource = () => {
        const log = global.logUtils.createContext('JobUtils', 'initResource');
        BattleDao.queryBattleTable([['room_status', ROOM_STATUS.BATTLE]])
            .then((battleData: any) => {
                if (battleData && battleData.length > 0) {
                    log.info(`本次初始化共需要恢复${battleData.length}条数据`);
                    for (let battle of battleData) {
                        if (!timeUserData.get(battle.id)) {
                            log.info(`开始恢复对战房间[${battle.id}]的数据`);
                            this.createBattleUserTime(battle.id)
                                .then(() => log.info(`对战房间[${battle.id}]的数据已恢复`));
                        }
                    }
                } else {
                    log.info(`本次初始化不需要恢复数据`);
                }
            });
    }

    /**
     * 初始化定时任务
     */
    initAllTask = (io: any) => {

        // 玩家对局超时倒计时
        setInterval(async () => {
            global.logUtils.createTraceId();
            const log = global.logUtils.createContext('JobUtils', '定时任务(对局超时)');
            for (let [battleId, userList] of timeUserData) {
                // 进行结算
                const [playOne, playTwo] = userList;
                // 拿最新的落子方(任意一方的数据即可)
                const isRedMove = await this.getBattleIsRedMove(battleId, playOne.userId);
                if (typeof isRedMove === 'boolean') {
                    // 判断出当前的计时方
                    const user = playOne.first === isRedMove ? playOne : playTwo;
                    if(user.stepTime >= 0 || user.allTime >= 0) {
                        // 步时
                        user.stepTime > 0 && (user.stepTime = user.stepTime - 1);
                        // 局时读尽时步时改成读秒
                        user.allTime === 1 && (user.stepTime = BATTLE.READ_SECONDS);
                        user.allTime > 0 && (user.allTime = user.allTime - 1);
                        // 步时超时后结算
                        if (user.stepTime <= 0) {
                            log.info(`[${battleId}]用户[${user.userId}]超时数据为`, user);
                            // 更新数据库对战状态
                            await BattleDao.updateBattleStatus(battleId, ROOM_STATUS.TIMEOUT);
                            // 清除倒计时
                            timeUserData.delete(battleId);
                            log.info(`[${battleId}]用户[${user.userId}]所在的房间[${user.roomId}]读秒超时`);
                            // 对游戏进行结算
                            lock.acquire('handleGameResultLock', (down: Function) => {
                                roomUtils.handleGameResult(battleId, user.userId, GAME_OVER_TYPE.USER_TIMEOUT)
                                    .then(() => { down(); })
                                    .catch(() => { down(); });
                            }, '');
                        }
                    }
                } else {
                    timeUserData.delete(battleId);
                    log.info(`对战区[${battleId}]房间内的用户已提前离开`);
                }
            }
        }, 1000);

        // 玩家离线保持会话倒计时
        setInterval(async () => {
            global.logUtils.createTraceId();
            const log = global.logUtils.createContext('JobUtils', '定时任务(离线超时通知)');

            const offlineBattleUsers: any = await BattleUserDao.getOfflineTimeoutUser(BATTLE.OFFLINE_TIME_SECONDS);
            if (offlineBattleUsers.length > 0) {
                for (let battleUser of offlineBattleUsers) {
                    const {battleId, userId, roomId} = battleUser;
                    // 查询该对战房间的游戏状态
                    const battleData: any = await BattleDao.queryBattleTable([['id', battleId], ['room_status', ROOM_STATUS.BATTLE_OVER]]);
                    if (battleData && battleData.length > 0) {
                        await BattleUserDao.updateBattleUserOfflineTime(battleId, userId, roomId,null);
                        log.warn(`用户[${userId}]所在的对战区[${battleId}]早已完成结算`);
                    } else {
                        // 进行游戏结算
                        lock.acquire('handleGameResultLock', (down: Function) => {
                            roomUtils.handleGameResult(battleId, userId, GAME_OVER_TYPE.USER_LEAVE)
                                .then(() => {
                                    // 结算完成离开房间
                                    roomUtils.leaveRoom(userId, roomId);
                                    log.info(`用户[${userId}]所在的对战区[${battleId}]结算完成`);
                                    down();
                                })
                                .catch(() => { down(); })
                        }, '');
                    }
                }
            }
        }, 1000);

        // 玩家已长时间与服务器断开了连接
        setInterval(async () => {
            global.logUtils.createTraceId();
            const log = global.logUtils.createContext('JobUtils', '定时任务(与服务器断开)');
            const disconnectTimeUsers: any = await UserStateDao.getDisconnectTimeUser(BATTLE.DISCONNECT_TIME_SECONDS);
            if (disconnectTimeUsers.length > 0) {
                for (let user of disconnectTimeUsers) {
                    const {userId, roomId} = user;
                    // 判断该用户是否为对战用户
                    const userState = await UserStateDao.getUserStateByUserId(userId);
                    log.info(`用户[${userId}]的游离数据为：`, userState);

                    if (userState && userState.userStatus === USER_STATUS.BATTLE) {
                        lock.acquire('handleGameResultLock', (down: Function) => {
                            log.info(`用户[${userId}]所在的房间[${roomId}]还在对战，开始结算(逃跑)`);
                            roomUtils.handleGameResult(userState.battleId, userId, GAME_OVER_TYPE.USER_LEAVE)
                                .then(() => {
                                    log.info(`让用户[${userId}]离开房间[${roomId}]`);
                                    roomUtils.leaveRoom(userId, roomId);
                                    log.info(`用户[${userId}]已离开对战房间[${roomId}]`);
                                    down();
                                })
                                .catch(() => { down(); });
                        }, '');
                    } else if (roomId) {
                        // 若用户在房间中，则离开房间
                        await roomUtils.leaveRoom(userId, roomId);
                        log.info(`用户[${userId}]已离开房间[${roomId}]`);
                    }

                    const columns = [];
                    columns.push(['room_id', null]);
                    columns.push(['room_status', null]);
                    columns.push(['first', null]);
                    columns.push(['is_ready', null]);
                    columns.push(['battle_id', null]);
                    columns.push(['user_status', null]);
                    columns.push(['disconnect_time', null]);
                    columns.push(['join_type', null]);
                    columns.push(['lock_pass', null]);
                    columns.push(['is_room_admin', null]);
                    columns.push(['user_page', PAGE_STATUS.PLATFORM]);
                    // 不重置token，下次同一个客户端连接时，可不需要登录直接操作
                    // columns.push(['token', null])
                    await UserStateDao.updateUserStateTable(columns, [['user_id', userId]]);
                    log.info(`用户[${userId}]已与服务器断开很长时间被重置游离数据`);
                }
            }
        }, 60 * 1000);
    }


    /**
     * 获取当前的落子方
     * @param battleId 对战号
     * @param userId 获取的账号
     */
    async getBattleIsRedMove(battleId: string, userId: string) {
        const log = global.logUtils.createContext("JobUtils", "getBattleIsRedMove", {userId, battleId});

        const mapVal = battleData.get(battleId);
        // 若取不到值时，从数据库中取
        if (!mapVal) {
            log.warn("从内存中取不到值，将从数据库中取值");
            const playOneHistory = await BattleHistoryDao.getLastBattleMapHistory(battleId, userId);
            return playOneHistory ? playOneHistory.isRedMove : null;
        }
        return mapVal.isRedMove;
    }

    /**
     * 更新对战的相关数据
     */
    updateBattleData = (battleId: string, data: any) => {
        const historyData = battleData.get(battleId) || {};
        battleData.set(battleId, {...historyData, ...data});
    }

    /**
     * 获取对战计时数据
     * @param battleId
     */
    getBattleTime = (battleId: string) => {
        return timeUserData.get(battleId);
    };

    /**
     * 移除对战倒计时（常在游戏结束结算时操作）
     * @param battleId
     */
    deleteAllBattleData = (battleId: string) => {
        // 清除对战数据
        battleData.delete(battleId);
        // 清除计时数据
        timeUserData.delete(battleId);
    };

    /**
     * 重置某个对战房间的步时
     * @param battleId
     * @param userId
     */
    resetBattleStepTime = (battleId: string, userId: string) => {
        const userList = timeUserData.get(battleId) || [];
        const user = userList.find((user: any) => user.userId === userId);
        if (user) {
            // 判断步时（是否需要进入读秒）
            user.stepTime = user.allTime <= 0 ? BATTLE.READ_SECONDS : BATTLE.STEP_TIME_SECONDS;
            timeUserData.set(battleId, userList);
        }
    };

    /**
     * 对局开始后，创建倒计时数据
     * @param battleId
     * @returns {Promise<boolean>}
     */
    createBattleUserTime = async (battleId: string) => {
        const log = global.logUtils.createContext('JobUtils', 'createBattleUserTime', {battleId});

        log.info(`开始为对战区[${battleId}]创建倒计时数据`);
        // 从数据库中获取房间的用户信息
        const battleUserData: any = await BattleUserDao.queryBattleUserTable([['battle_id', battleId]]);
        if (battleUserData.length < 2) {
            log.info(`对战区[${battleId}]的用户异常(length < 2)`);
            return false;
        }
        const [playOne, playTwo] = battleUserData;
        const playOneHistory = await BattleHistoryDao.getLastBattleMapHistory(battleId, playOne.userId);
        const playTwoHistory = await BattleHistoryDao.getLastBattleMapHistory(battleId, playTwo.userId);

        const timeUsers = [];
        if (playOneHistory && playTwoHistory) {
            timeUsers.push({
                userId: playOne.userId,
                allTime: playOneHistory.allTime,
                stepTime: playOneHistory.stepTime,
                first: playOne.first,
                roomId: playOne.roomId,
            });
            timeUsers.push({
                userId: playTwo.userId,
                allTime: playTwoHistory.allTime,
                stepTime: playTwoHistory.stepTime,
                first: playTwo.first,
                roomId: playOne.roomId,
            });
            timeUserData.set(battleId, timeUsers);
            log.info(`对战区[${battleId}]对局倒计时数据已创建`, timeUsers);

            // 继续创建双方棋盘数据
            this.updateBattleData(battleId, {isRedMove: playOneHistory.isRedMove});
        } else {
            log.info(`对战区[${battleId}]无历史对局数据`);
        }
    };
}

export default JobUtils;